import axios from "axios";
import PixivTokens from "../model/PixivTokens.mjs";
import AccessTokenRefreshFailed from "../error/AccessTokenRefreshFailed.mjs";
import LoginInterrupted from "../error/LoginInterrupted.mjs";
import LoginFailed from "../error/LoginFailed.mjs";

export class PixivLogin {
    static LOGIN_URL = 'https://app-api.pixiv.net/web/v1/login'
    static REDIRECT_URI = 'https://app-api.pixiv.net/web/v1/users/auth/pixiv/callback'
    static TOKEN_URL = "https://oauth.secure.pixiv.net/auth/token"
    static CLIENT_ID = 'MOBrBDS8blbauoSck0ZfDbtuzpyT'
    static CLIENT_SECRET = "lsACyCD94FhDUtGTXi3QzcFE2uU1hqtDaKeqrdwj"
    static USER_AGENT = "PixivAndroidApp/5.0.234 (Android 11; Pixel 5)"
    pluginTool
    codeVerifier
    codeChallenge

    constructor(pluginTool) {
        this.pluginTool = pluginTool
        this.codeVerifier = PixivLogin.generateCodeVerifier()
        this.codeChallenge = PixivLogin.generateCodeChallenge(this.codeVerifier)
    }

    /**
     * 弹窗请求用户登录pixiv
     * @return {Promise<PixivTokens>}
     */
    async loginPixiv() {
        const clientAuthCode = await this.getClientAuthCode()
        return this.exchangeToken(clientAuthCode)
    }

    /**
     * 获取客户端授权code
     * @return {Promise<string>}
     */
    async getClientAuthCode() {
        return new Promise(async (resolve, reject) => {
            const loginWindow = this.pluginTool.getBrowserWindow()

            let authCode
            loginWindow.webContents.on('will-redirect', (event, newUrl) => {
                // 检查是否是 pixiv:// 协议的链接
                if (newUrl.startsWith('pixiv://')) {
                    try {
                        // 使用 URL 解析提取 code 参数
                        const urlObj = new URL(newUrl)
                        authCode = urlObj.searchParams.get('code')
                    } catch (err) {
                        loginWindow.close()
                        reject(new LoginFailed(err))
                    }
                    if (authCode) {
                        // 阻止 BrowserWindow 加载这个无效的 pixiv:// 链接，因为系统没有关联的应用，加载会失败或空白
                        event.preventDefault()
                        // 关闭登录窗口
                        loginWindow.close()
                        resolve(authCode)
                    } else {
                        loginWindow.close()
                        reject(new LoginFailed('pixiv://请求中没有找到code'))
                    }
                }
                // 如果 newUrl 不是 pixiv://，允许正常导航（用户在登录流程中）
            })

            // 如果用户长时间不操作或登录失败
            const timeoutMs = 5 * 60 * 1000 // 5 minutes
            const timeout = setTimeout(() => {
                if (!authCode) {
                    loginWindow.close()
                    reject(new LoginInterrupted('登录超时'))
                }
            }, timeoutMs)

            // 监听窗口关闭事件（用户手动关闭）
            loginWindow.on('close', () => {
                // 清理超时定时器
                clearTimeout(timeout)
                // 如果此时还没有 code，说明用户取消了
                if (!authCode) {
                    reject(new LoginInterrupted('用户取消登录'))
                }
            })

            const finalUrl = PixivLogin.LOGIN_URL +
                `?code_challenge=${await this.codeChallenge}` +
                '&code_challenge_method=S256&client=pixiv-android' +
                `&redirect_uri=${PixivLogin.REDIRECT_URI}`
            // 加载登录页
            loginWindow.loadURL(finalUrl)
        })
    }

    /**
     * 从认证服务换取token
     * @param clientAuthCode 客户端授权code
     * @return {Promise<PixivTokens>}
     */
    async exchangeToken(clientAuthCode) {
        const body = {
            "client_id": PixivLogin.CLIENT_ID,
            "client_secret": PixivLogin.CLIENT_SECRET,
            "grant_type": "authorization_code",
            "code": clientAuthCode,
            "code_verifier": this.codeVerifier,
            "redirect_uri": PixivLogin.REDIRECT_URI,
            "include_policy": "true",
        }
        const headers = {
            "User-Agent": PixivLogin.USER_AGENT,
            "Content-Type": "application/x-www-form-urlencoded"
        }
        const response = await axios.post(PixivLogin.TOKEN_URL, body, { headers: headers })
        return new PixivTokens(
            response.data.access_token,
            response.data.refresh_token,
            response.data.expires_in,
            response.data.token_type,
            response.data.scope
        )
    }

    /**
     * 从认证服务刷新token
     * @param refreshToken 刷新令牌
     * @return {Promise<PixivTokens>}
     */
    static async refreshToken(refreshToken) {
        const body = {
            "client_id": PixivLogin.CLIENT_ID,
            "client_secret": PixivLogin.CLIENT_SECRET,
            "grant_type": "refresh_token",
            "include_policy": "true",
            "refresh_token": refreshToken
        }
        const headers = {
            "User-Agent": PixivLogin.USER_AGENT,
            "Content-Type": "application/x-www-form-urlencoded"
        }
        try {
            const response = await axios.post(PixivLogin.TOKEN_URL, body, { headers: headers })
            return new PixivTokens(
                response.data.access_token,
                response.data.refresh_token,
                response.data.expires_in,
                response.data.token_type,
                response.data.scope
            )
        } catch (error) {
            const systemError = error.response?.data?.errors?.system
            const errorCode = systemError?.code
            const errorMsg = systemError?.message
            // TODO 1508是refresh_token不可用的响应码，过期的还不知道是什么
            const msg = "刷新token失败！" + errorMsg
            if ([1508].includes(errorCode)) {
                throw new AccessTokenRefreshFailed(msg)
            } else {
                throw new Error(msg)
            }
        }
    }

    /**
     * 生成 code_verifier (长度为 64 的 URL 安全 Base64 字符串)
     * @return {string}
     */
    static generateCodeVerifier() {
        // 生成 43 个随机字节 (因为 base64url 编码 43 字节 ≈ 57-58 个字符，但通常足够长且安全)
        // 或者生成 64 个字节的随机数据，然后编码
        const array = new Uint8Array(64) // 64 bytes
        crypto.getRandomValues(array)
        // 将字节数组转换为 base64url 字符串
        return btoa(String.fromCharCode(...array))
            .replace(/\+/g, '-')  // 将 '+' 替换为 '-'
            .replace(/\//g, '_')  // 将 '/' 替换为 '_')
            .replace(/=/g, '')   // 移除 '=' 填充
    }

    /**
     * 从 code_verifier 生成 code_challenge (SHA-256 哈希后 base64url 编码)
     * @param codeVerifier
     * @return {Promise<string>}
     */
    static async generateCodeChallenge(codeVerifier) {
        const encoder = new TextEncoder()
        const data = encoder.encode(codeVerifier)
        // 使用 Web Crypto API 进行 SHA-256 哈希
        const hashBuffer = await crypto.subtle.digest('SHA-256', data)
        const hashArray = new Uint8Array(hashBuffer)
        // 将哈希值转换为 base64url 字符串
        return btoa(String.fromCharCode(...hashArray))
            .replace(/\+/g, '-')  // 将 '+' 替换为 '-'
            .replace(/\//g, '_')  // 将 '/' 替换为 '_'
            .replace(/=/g, '')   // 移除 '=' 填充
    }
}
